package com.example.base_model.ext

import android.view.View
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.util.WeakHashMap

/**
 * @Author jiangKunKun
 * @CreateDate 2025/7/24
 * @Description 为 View 点击事件提供防抖（debounce）和节流（throttle）功能的工具类和扩展函数。
 * @SpittingGrooves
 */

/**
 * 一个单例对象，作为处理点击事件的核心逻辑单元。
 * 它内部管理点击时间戳和协程作业，以实现复杂的点击行为控制。
 */
object ClickGuard {
    /**
     * 存储每个 View 的最后一次点击时间。
     * 使用 [WeakHashMap] 是为了防止内存泄漏。当 View 对象被垃圾回收时，
     * 它在 map 中对应的条目也会被自动移除，因为 `WeakHashMap` 不会阻止其键被回收。
     */
    private val lastClickTimes = WeakHashMap<View, Long>()

    /**
     * 默认的点击时间间隔（毫秒）。
     */
    private const val DEFAULT_INTERVAL = 500L

    /**
     * 实现点击事件防抖（Debounce）。
     * 在指定的时间间隔内，只有第一次点击会触发 `action`。
     *
     * @param view 被点击的 View。
     * @param interval 防抖的时间间隔（毫秒）。
     * @param action 要执行的点击操作。
     */
    fun click(view: View, interval: Long = DEFAULT_INTERVAL, action: (View) -> Unit) {
        // 1. 获取当前系统时间（毫秒）
        val currentTime = System.currentTimeMillis()
        // 2. 从 WeakHashMap 中获取该 View 上次的点击时间，如果不存在则默认为 0
        val lastTime = lastClickTimes[view] ?: 0
        // 3. 计算两次点击之间的时间差
        val timeDiff = currentTime - lastTime

        // 4. 判断点击是否有效
        //    - `timeDiff > interval`: 距离上次点击已经超过了指定的间隔，是有效点击。
        //    - `timeDiff < 0`: 系统时间被向后修改，导致时间差为负数。为了避免按钮被永久锁定，也视为有效点击。
        //    - `!in 0..interval` 是 `timeDiff < 0 || timeDiff > interval` 的简洁写法。
        if (timeDiff !in 0..interval) {
            // 5. 如果点击有效，则更新 map 中该 View 的最后点击时间
            lastClickTimes[view] = currentTime
            // 6. 执行传入的点击操作
            action(view)
        }
    }

    /**
     * 存储与每个 View 关联的延迟点击协程作业。
     * 使用 [WeakHashMap] 以避免内存泄漏。
     */
    private val delayJobs = WeakHashMap<View, Job>()

    /**
     * 实现点击事件节流（Throttle），也称为延迟执行。
     * 当用户连续点击时，会取消之前的计划，只在最后一次点击发生后的指定延迟后执行 `action`。
     *
     * @param view 被点击的 View。
     * @param delayMillis 延迟执行的时间（毫秒）。
     * @param scope 用于启动协程的 CoroutineScope。
     * @param action 延迟后要执行的操作。
     */
    fun delayClick(
        view: View,
        delayMillis: Long = 500L,
        scope: CoroutineScope = CoroutineScope(Dispatchers.Main + SupervisorJob()),
        action: (View) -> Unit
    ) {
        delayJobs[view]?.cancel() // 取消与此 View 关联的任何先前计划的任务
        delayJobs[view] = scope.launch {
            delay(delayMillis) // 等待指定的延迟时间
            action(view) // 执行操作
        }

        // 添加一个监听器，在 View 从窗口分离时自动清理资源
        view.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
            override fun onViewAttachedToWindow(v: View) {}
            override fun onViewDetachedFromWindow(v: View) {
                // 当 View 不再可见时，取消任何挂起的作业并从 map 中移除
                delayJobs[v]?.cancel()
                delayJobs.remove(v)
                v.removeOnAttachStateChangeListener(this) // 清理监听器本身
            }
        })
    }
}

/**
 * 为 View 设置一个防抖的点击监听器。
 * 在指定的时间间隔内，只有第一次点击是有效的。
 *
 * @param interval 防抖的时间间隔（毫秒）。默认为 500ms。
 * @param action 当有效点击发生时要执行的操作。
 *
 * **用法示例:**
 * ```
 * myButton.setOnSingleClickListener {
 *     // 这段代码在 500ms 内只会被执行一次
 * }
 * ```
 */
fun View.setOnSingleClickListener(interval: Long = 500, action: (View) -> Unit) {
    this.setOnClickListener {
        ClickGuard.click(this, interval, action)
    }
}

/**
 * 为 View 设置一个延迟执行的点击监听器（节流）。
 * 如果在指定延迟时间内发生多次点击，只有最后一次点击会触发操作。
 *
 * **注意**: 此版本创建了一个独立的 `CoroutineScope`。对于在 Activity 或 Fragment 中使用的场景，
 * 推荐使用 `setOnDelayClickListenerInScope` 并传入一个生命周期感知的 Scope（如 `lifecycleScope`），
 * 以更好地管理协程的生命周期。
 *
 * @param delayMillis 延迟执行的时间（毫秒）。默认为 500ms。
 * @param scope 用于启动协程的 CoroutineScope。
 * @param action 延迟后要执行的操作。
 *
 * **用法示例:**
 * ```
 * searchButton.setOnDelayClickListener(delayMillis = 300) {
 *     // 用户停止点击 300ms 后执行搜索
 * }
 * ```
 */
fun View.setOnDelayClickListener(
    delayMillis: Long = 500L,
    scope: CoroutineScope = CoroutineScope(Dispatchers.Main + SupervisorJob()),
    action: (View) -> Unit
) {
    setOnClickListener {
        ClickGuard.delayClick(this, delayMillis, scope, action)
    }
}

/**
 * 为 View 设置一个与生命周期绑定的、延迟执行的点击监听器（节流）。
 * 这是处理延迟点击最推荐的方式，因为它将协程作业与传入的 `scope` (如 `lifecycleScope` 或 `viewModelScope`) 绑定，
 * 从而自动处理取消操作，防止内存泄漏和不必要的操作。
 *
 * @param delayMillis 延迟执行的时间（毫秒）。默认为 500ms。
 * @param scope 一个生命周期感知的 CoroutineScope，例如 `viewLifecycleOwner.lifecycleScope` 或 `viewModelScope`。
 * @param action 延迟后要执行的操作。
 *
 * **用法示例 (在 Fragment 中):**
 * ```
 * myButton.setOnDelayClickListenerInScope(scope = viewLifecycleOwner.lifecycleScope) {
 *     // 当 Fragment 销毁时，这个协程会自动取消
 *     viewModel.performAction()
 * }
 * ```
 */
fun View.setOnDelayClickListenerInScope(
    delayMillis: Long = 500L,
    scope: CoroutineScope, // 必须传入 ViewModelScope 或 LifecycleScope
    action: (View) -> Unit
) {
    // 1. 定义一个 Job? 变量来持有当前正在计划中的协程任务。
    var job: Job? = null
    setOnClickListener {
        // 2. 当发生新的点击时，立即取消上一个还未执行的协程任务（如果存在）。
        //    这就是实现“节流”效果的关键：只有最后一次点击的计划会最终执行。
        job?.cancel()
        // 3. 在传入的、与生命周期绑定的 scope 中启动一个新的协程。
        job = scope.launch {
            // 4. 等待指定的延迟时间。
            delay(delayMillis)
            // 5. 延迟结束后，执行最终的点击操作。
            //    使用 this@setOnDelayClickListenerInScope 来明确指代接收者 View。
            action(this@setOnDelayClickListenerInScope)
        }
    }
}
